package demoMod.stsconsole.core;

import demoMod.stsconsole.ConsoleEntry;
import demoMod.stsconsole.cards.AbstractCard;
import demoMod.stsconsole.characters.AbstractPlayer;
import demoMod.stsconsole.dungeons.AbstractDungeon;
import demoMod.stsconsole.helpers.LocalizedStrings;
import demoMod.stsconsole.helpers.PromptHelper;
import demoMod.stsconsole.helpers.SaveHelper;
import demoMod.stsconsole.helpers.SubscriberReturnValueSaver;
import demoMod.stsconsole.monsters.AbstractMonster;
import demoMod.stsconsole.powers.AbstractPower;
import demoMod.stsconsole.rooms.AbstractRoom;
import demoMod.stsconsole.rooms.MonsterRoom;
import demoMod.stsconsole.interfaces.*;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

public class CombatManager implements ReloadStringsSubscriber {
    public TurnsOwner turnsOwner;
    public Phase phase;
    public boolean shouldDropReward = true;
    private int currEnergy;
    public int turn;
    public Consumer<Void> defaultActionAtTheEndOfTurn = v -> AbstractDungeon.player.discardAll();
    private List<String> uiStrings;
    private final String encounterId;

    public CombatManager(String encounterId) {
        this.turnsOwner = TurnsOwner.PLAYER;
        this.phase = Phase.START;
        this.turn = 0;
        this.encounterId = encounterId;
        ConsoleEntry.addLater.add(this);
        onReloadStrings();
    }

    public void startCombat() {
        ConsoleEntry.triggerSubscribers(CombatStartSubscriber.class, "atBattleStart");
        for (ISubscriber subscriber : ConsoleEntry.subscribers) {
            if (subscriber instanceof AbstractCreature && !(subscriber instanceof AbstractPlayer)) {
                AbstractCreature creature = (AbstractCreature) subscriber;
                if (!AbstractDungeon.currDungeon.currMapNode.room.monsters.monsters.contains(creature)) {
                    creature.onRemove();
                }
            }
        }
        for (AbstractCreature creature : AbstractDungeon.currDungeon.currMapNode.room.monsters.monsters) {
            if (creature instanceof AbstractMonster) {
                AbstractMonster monster = (AbstractMonster) creature;
                monster.usePreBattleAction();
                monster.rollMove();
                monster.gainGold();
            }
        }
        switch (this.phase) {
            case START:
                startTurn();
                break;
            case END:
                endTurn();
        }
    }

    public void startTurn() {
        System.out.println(uiStrings.get(0));
        switch (this.turnsOwner) {
            case PLAYER:
                System.out.println(uiStrings.get(1));
                this.currEnergy = AbstractDungeon.player.numEnergyGainPerTurn;
                ConsoleEntry.triggerSubscribers(EnergyResetSubscriber.class, "onEnergyReset",
                        new Class[]{int.class},
                        new Object[]{this.currEnergy},
                        (method, params, subscriber) -> {
                            try {
                                params[0] = method.invoke(subscriber, params);
                                this.currEnergy = (int) params[0];
                            } catch (IllegalAccessException | InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        }
                );
                AbstractDungeon.player.currEnergy = this.currEnergy;
                AbstractDungeon.player.draw(AbstractDungeon.player.numCardsDrawPerTurn, true);
                this.turn++;
                {
                    SubscriberReturnValueSaver returnValueSaver = new SubscriberReturnValueSaver();
                    returnValueSaver.intValue = AbstractDungeon.player.currentBlock;
                    ConsoleEntry.triggerSubscribers(TurnStartLoseBlockSubscriber.class, "atTurnStartLoseBlock",
                            new Class[]{AbstractCreature.class}, new Object[]{AbstractDungeon.player},
                            (method, params, subscriber) -> {
                                try {
                                    returnValueSaver.intValue = Math.min(returnValueSaver.intValue, (int) method.invoke(subscriber, params));
                                } catch (IllegalAccessException | InvocationTargetException e) {
                                    e.printStackTrace();
                                }
                            }
                    );
                    AbstractDungeon.player.currentBlock -= returnValueSaver.intValue;
                }
                ConsoleEntry.triggerSubscribers(TurnStartSubscriber.class, "atTurnStart", new Class[]{boolean.class}, new Object[]{true});
                break;
            case ENEMY:
                System.out.println(uiStrings.get(2));
                for (AbstractCreature creature : AbstractDungeon.currDungeon.currMapNode.room.monsters.monsters) {
                    if (creature instanceof AbstractMonster && !creature.isDead) {
                        AbstractMonster monster = (AbstractMonster) creature;
                        {
                            SubscriberReturnValueSaver returnValueSaver = new SubscriberReturnValueSaver();
                            returnValueSaver.intValue = monster.currentBlock;
                            ConsoleEntry.triggerSubscribers(TurnStartLoseBlockSubscriber.class, "atTurnStartLoseBlock",
                                    new Class[]{AbstractCreature.class}, new Object[]{monster},
                                    (method, params, subscriber) -> {
                                        try {
                                            returnValueSaver.intValue = Math.min(returnValueSaver.intValue, (int) method.invoke(subscriber, params));
                                        } catch (IllegalAccessException | InvocationTargetException e) {
                                            e.printStackTrace();
                                        }
                                    }
                            );
                            monster.currentBlock -= returnValueSaver.intValue;
                        }
                    }
                }
            default:
                ConsoleEntry.triggerSubscribers(TurnStartSubscriber.class, "atTurnStart", new Class[]{boolean.class}, new Object[]{false});
                break;
        }
        if (AbstractDungeon.player.isDead) {
            endCombat(false);
            return;
        }
        this.phase = Phase.DURING;
        duringTurn();
    }

    public void duringTurn() {
        switch (this.turnsOwner) {
            case PLAYER:
                if (AbstractDungeon.player.isDead) endCombat(false);
                break;
            case ENEMY:
                for (AbstractCreature creature : AbstractDungeon.currDungeon.currMapNode.room.monsters.monsters) {
                    if (creature instanceof AbstractMonster && !creature.isDeadOrEscaped()) {
                        AbstractMonster monster = (AbstractMonster) creature;
                        if (!monster.getCurrentMove().moveName.equals("")) {
                            monster.talk(uiStrings.get(4) + monster.getCurrentMove().moveName);
                        }
                        monster.applyPowers();
                        monster.takeTurn();
                        if (AbstractDungeon.player.isDead) {
                            endCombat();
                            return;
                        }
                    }
                }
                AbstractDungeon.currDungeon.currMapNode.room.monsters.addSpawnedMonsters();
                if (isEnemyBasicallyDead()) {
                    endCombat();
                    return;
                }
                endTurn();
                break;
        }
    }

    public void endTurn() {
        switch (this.turnsOwner) {
            case PLAYER:
                System.out.println(uiStrings.get(1) + uiStrings.get(3));
                this.turnsOwner = TurnsOwner.ENEMY;
                ConsoleEntry.triggerSubscribers(TurnEndSubscriber.class, "atEndOfTurn", new Class[]{boolean.class}, new Object[]{true});
                if (isEnemyBasicallyDead()) {
                    endCombat();
                    return;
                }
                if (AbstractDungeon.player.isDead) {
                    endCombat(false);
                    return;
                }
                List<AbstractCard> toRemove = new ArrayList<>();
                for (AbstractCard card : AbstractDungeon.player.hand) {
                    if (card.isEthereal) {
                        ConsoleEntry.triggerSubscribers(ExhaustCardSubscriber.class, "onExhaustCard", new Class[]{AbstractCard.class}, new Object[]{card});
                        toRemove.add(card);
                        AbstractDungeon.player.exhaustPile.add(card);
                    }
                }
                AbstractDungeon.player.hand.removeAll(toRemove);

                defaultActionAtTheEndOfTurn.accept(null);
                for (AbstractCard card : AbstractDungeon.player.hand) {
                    if (card.retain) {
                        ConsoleEntry.triggerSubscribers(RetainCardSubscriber.class, "onRetainCard", new Class[]{AbstractCard.class}, new Object[]{card});
                    }
                }
                break;
            case ENEMY:
                System.out.println(uiStrings.get(2) + uiStrings.get(3));
                this.turnsOwner = TurnsOwner.PLAYER;
                ConsoleEntry.triggerSubscribers(TurnEndSubscriber.class, "atEndOfTurn", new Class[]{boolean.class}, new Object[]{false});
                break;
        }
        startTurn();
    }

    public void endCombat() {
        endCombat(this.shouldDropReward);
    }

    public void endCombat(boolean dropReward) {
        if (!AbstractDungeon.player.isDead) {
            ConsoleEntry.triggerSubscribers(PostCombatSubscriber.class, "onVictory");
            PromptHelper.backwardPrompt();
            AbstractDungeon.currDungeon.currMapNode.room.phase = AbstractRoom.RoomPhase.COMPLETED;
            for (AbstractPower power : AbstractDungeon.player.getPowers()) {
                if (!power.isDone) power.onRemove();
            }
            AbstractDungeon.currDungeon.currMapNode.room.shouldDropReward = dropReward;
            if (AbstractDungeon.currDungeon.weakEnemyPool.contains(this.encounterId)) {
                AbstractDungeon.currDungeon.lastWeakEnemyEncounter = this.encounterId;
                AbstractDungeon.currDungeon.weakEnemyCtr++;
            } else if (AbstractDungeon.currDungeon.strongEnemyPool.contains(this.encounterId)) {
                AbstractDungeon.currDungeon.lastStrongEnemyEncounter = this.encounterId;
            } else if (AbstractDungeon.currDungeon.eliteEnemyPool.contains(this.encounterId)) {
                AbstractDungeon.currDungeon.lastEliteEnemyEncounter = this.encounterId;
            }
            if (AbstractDungeon.currDungeon.currMapNode.room instanceof MonsterRoom) SaveHelper.save();
            AbstractDungeon.currDungeon.currMapNode.room.dropReward();
            System.out.println(uiStrings.get(5));
        }
        ConsoleEntry.removeLater.add(this);
    }

    public boolean isEnemyBasicallyDead() {
        return AbstractDungeon.currDungeon.currMapNode.room.monsters.isEnemyBasicallyDead();
    }

    @Override
    public void onReloadStrings() {
        uiStrings = LocalizedStrings.getUIStrings("CombatManager");
    }

    public enum TurnsOwner {
        PLAYER, ENEMY
    }

    public enum Phase {
        START, DURING, END
    }
}
